package com.gzz.orchid.common.core.support;


import java.lang.management.ManagementFactory;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.random.RandomGenerator;

/**
 * 雪花算法序生成器
 *
 * @author higmd
 */
public class SnowFlake {
    //================================================Algorithm's Parameter=============================================

    // ============================== 单例==========================================
    private static SnowFlake worker = null;
    // 这个时间越久远，生成的ID就越长。
    //开始时间截 时间起始标记点，作为基准，一般取系统的最近时间（一旦确定不能变动）
    private final long START_TIME = 1498608000000L;
    // 机器id所占的位数
    private final long WORDER_ID_BITS = 5L;
    // 数据标识id所占的位数
    private final long DATA_CENTER_ID_BITS = 5L;
    // 支持的最大机器id(十进制) ，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
    // -1L 左移 5位 (worker id 所占位数) 即 5位二进制所能获得的最大十进制数 - 31
    private final long MAX_WORKER_ID = -1L ^ (-1L << WORDER_ID_BITS);
    // 支持的最大数据标识id - 31
    private final long MAX_DATA_CENTER_ID = -1L ^ (-1L << DATA_CENTER_ID_BITS);
    // 序列在id中占的位数
    private final long SEQUENCE_BITS = 12L;
    // 机器ID 左移位数 - 12 (即末 sequence 所占用的位数)
    private final long WORKER_ID_MOVE_BITS = SEQUENCE_BITS;
    // 数据标识id 向左移17位(12+5)
    private final long DATA_CENTER_ID_MOVE_BITS = SEQUENCE_BITS + WORDER_ID_BITS;
    // 时间截向左移22位(5+5+12)
    private final long TIEMSTAMP_MOVE_BITS = SEQUENCE_BITS + WORDER_ID_BITS + DATA_CENTER_ID_BITS;

    //=================================================Works's Fields================================================
    // 生成序列的掩码(12位所对应的最大整数值)，这里为4095 (0b111111111111=0xfff=4095)
    private final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);
    /**
     * 工作机器ID(0~31)
     */
    private final long workerId;
    /**
     * 数据中心ID(0~31)
     */
    private final long dataCenterId;
    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

    //===============================================Constructors=======================================================
    /**
     * 上次生成ID的时间截
     */
    private long lastTimestamp = -1L;

    /**
     * 构造函数
     *
     * @param workerId     工作ID (0~31)
     * @param dataCenterId 数据中心ID (0~31)
     */
    public SnowFlake(long workerId, long dataCenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            //throw new IllegalArgumentException(String.format("Worker I'd can't be greater than %d or less than 0", maxWorkerId));
            throw new IllegalArgumentException(String.format("工作进程Id不能大于%d或小于0", MAX_WORKER_ID));
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("DataCenter Id can't be greater than %d or less than 0", MAX_DATA_CENTER_ID));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }
    // ==============================CORE Methods==========================================

    public SnowFlake() {
        this.workerId = getWorkId();
        this.dataCenterId = getDataCenterId();
        System.out.println("workerId::" + this.workerId);
        System.out.println("dataCenterId::" + this.dataCenterId);
    }

    /**
     * 获取 maxWorkerId
     */
    private static long getWorkerId(long dataCenterId, long maxWorkerId) {
        StringBuilder mpid = new StringBuilder();
        mpid.append(dataCenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        //System.out.println("RuntimeMXBean名称：" +name);
        if (!name.isEmpty()) {
            // GET jvmPid
            mpid.append(name.split("@")[0]);
        }
        // MAC + PID 的 hashcode 获取16个低位
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }
    // ============================== NORMAL Methods==========================================

    /**
     * 根据主机名称 生成 DataCenterId
     */
    private static Long getDataCenterId() {
        //System.out.println("主机名称：" +SystemUtils.getHostName());
        //int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName());
        int[] ints = new int[9];
        int sums = 0;
        for (int i : ints) {
            sums += i;
        }
        return (long) (sums % 32);

    }

    public static SnowFlake instance() {
        if (worker == null) {
            synchronized (SnowFlake.class) {
                if (worker == null) {
                    worker = new SnowFlake();
                }
                return worker;
            }
        }
        return worker;
    }

    /**
     * 测试
     */
    public static void main(String[] args) throws InterruptedException {
        SnowFlake idworker = new SnowFlake();
        long timestamp = System.currentTimeMillis();
        System.out.println(timestamp);
        System.out.println(timestamp & 1);
        long startTime = System.nanoTime();
        //
        for (int i = 0; i < 50000; i++) {
            Thread.sleep(100);
            long id = idworker.nextId();
            System.out.println(id);
        }
        System.out.println((System.nanoTime() - startTime) / 1000000 + "ms");
    }

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的，则进行毫秒内序列
        //获取当前时间戳如果等于上次时间戳（同一毫秒内），则在序列号加一；否则序列号赋值为0，从0开始。
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            //毫秒内序列溢出，即 序列 > 4095
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变，毫秒内序列重置
        else {
            // 最后一位
            sequence = timestamp & 1;
        }
        //上次生成ID的时间截 将上次时间戳值刷新
        lastTimestamp = timestamp;

        /*
          返回结果：
          | 是按位或运算符，例如：x | y，只有当x，y都为0的时候结果才为0，其它情况结果都为1。
          因为个部分只有相应位上的值有意义，其它位上都是0，所以将各部分的值进行 | 运算就能得到最终拼接好的id
         */
        //ID偏移组合生成最终的ID,移位并通过或运算拼到一起组成64位的ID
        //表示将时间戳减去初始时间戳，再左移相应位数
        return ((timestamp - START_TIME) << TIEMSTAMP_MOVE_BITS)
                //表示将数据id左移相应位数
                | (dataCenterId << DATA_CENTER_ID_MOVE_BITS)
                //表示将工作id左移相应位数
                | (workerId << WORKER_ID_MOVE_BITS)
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 根据IP地址生成 WorkerId
     */
    private Long getWorkId() {
        try {
            String hostAddress = Inet4Address.getLocalHost().getHostAddress();
            //System.out.println("hostAddress名称：" +hostAddress);
            // int[] ints = StringUtils.toCodePoints(hostAddress);
            int[] ints = new int[hostAddress.length()];
            int sums = 0;
            for (int b : ints) {
                sums += b;
            }
            return (long) (sums % 32);
        } catch (UnknownHostException e) {
            // 如果获取失败，则使用随机数备用
            return RandomGenerator.getDefault().nextLong(0, 31);
        }
    }
    //==============================Test=============================================

    /**
     * 数据标识id部分
     */
    private long getDataCenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = ((0x000000FF & (long) mac[mac.length - 1])
                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (this.MAX_DATA_CENTER_ID + 1);
            }
        } catch (Exception e) {
            System.out.println(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }
}
